﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using AdvUtils;
using CRFSharp;
using GeoUtils;

namespace BusinessNameParserFeatureGenerator
{
    public class GeoDictMatchResult
    {
        public int offset;
        public int len;
        public int hasRelation;
        public string strGeoName;
    }

    public class SubSuffixToken
    {
        public int offset;
        public int len;
    }

    public class BusinessNameParserFeatureGenerator : IGenerateFeature
    {
        DictMatch dictmatch = null;
        List<Lemma> dm_r;
        List<int> dm_offsetList;
        Dictionary<int, int> adminCode2PreCode;

        DoubleArrayTrieSearch dartSearch_GeoEntity;
        List<List<GeoEntity>> vaGeoEntityList;


        const string KEY_LEXICAL_DICT_FILE_NAME = "DictMatchFileName";
        const string KEY_BINARY_DICT = "BinaryDictFile";
        const string KEY_GEO_ENTITY_FILE_NAME = "GeoEntityFileName";
        const string KEY_ADMIN_CODE_FILE_NAME = "AdminCodeFileName";

        private Dictionary<string, string> LoadConfFile(string strConfFileName)
        {
            Dictionary<string, string> dict = new Dictionary<string, string>();
            StreamReader sr = new StreamReader(strConfFileName);
            while (sr.EndOfStream == false)
            {
                string strLine = sr.ReadLine();
                string[] items = strLine.Split('=');

                items[0] = items[0].ToLower().Trim();
                items[1] = items[1].ToLower().Trim();

                if (items[0] != KEY_ADMIN_CODE_FILE_NAME.ToLower() &&
                    items[0] != KEY_BINARY_DICT.ToLower() &&
                    items[0] != KEY_LEXICAL_DICT_FILE_NAME.ToLower() &&
                    items[0] != KEY_GEO_ENTITY_FILE_NAME.ToLower())
                {
                    throw new Exception("Invalidated configuration item");

                }
                dict.Add(items[0], items[1]);
            }

            sr.Close();

            return dict;
        }


        private List<SubSuffixToken> GetSubSuffixTagList(string strLine)
        {
            string[] subSuffixList = { "分公司", "办事处", "总代理", "分行", "分院", 
                                     "办事处", "代表处", "营业部", "联络处", 
                                     "总经销", "驻", "工作站", "分所", 
                                     "直销处", "校区", "工作处", "加盟店", 
                                     "经销点", "销售代表处", "销售处", "大使馆", 
                                     "分园", "贸易分公司", "分校", "教学区", 
                                     "项目部", "总领事馆"};

            List<SubSuffixToken> sstList = new List<SubSuffixToken>();
            foreach (string subSuffix in subSuffixList)
            {
                int pos = -1;
                pos = strLine.LastIndexOf(subSuffix);
                if (pos >= 0)
                {
                    SubSuffixToken sst = new SubSuffixToken();
                    sst.offset = pos;
                    sst.len = subSuffix.Length;

                    sstList.Add(sst);
                }
            }

            return sstList;
        }


        //Load admin code ontology from file
        private void LoadAdminCodeRelationship(string strAdminCodeOntologyFileName)
        {
            adminCode2PreCode = new Dictionary<int, int>();

            StreamReader sr = new StreamReader(strAdminCodeOntologyFileName);
            while (sr.EndOfStream == false)
            {
                string strLine = sr.ReadLine();
                string[] items = strLine.Split('\t');
                adminCode2PreCode.Add(int.Parse(items[0]), int.Parse(items[1]));
            }
            sr.Close();
        }

        public int GetTopLevel(string strGeoEntity)
        {
            int offset = dartSearch_GeoEntity.SearchByPerfectMatch(strGeoEntity);
            if (offset < 0)
            {
                return -1;
            }

            int iTopLevel = 100;
            List<GeoEntity> geoEntityList = vaGeoEntityList[offset];
            foreach (GeoEntity geoEntity in geoEntityList)
            {
                if (geoEntity.iLevel <= iTopLevel)
                {
                    iTopLevel = geoEntity.iLevel;
                }
            }
            return iTopLevel;
        }


        //Check whether strGeoEntity2 is the parent of strGeoEntity1
        public bool IsParent(string strGeoEntity1, string strGeoEntity2)
        {
            //retrieval geo-entities from indexed geo entity data by token's term.
            int offset1 = dartSearch_GeoEntity.SearchByPerfectMatch(strGeoEntity1);
            if (offset1 < 0)
            {
                return false;
            }

            int offset2 = dartSearch_GeoEntity.SearchByPerfectMatch(strGeoEntity2);
            if (offset2 < 0)
            {
                return false;
            }

            List<GeoEntity> geoEntityList1 = vaGeoEntityList[offset1];
            List<GeoEntity> geoEntityList2 = vaGeoEntityList[offset2];

            //Get all possible admin code of geo-entity 2
            HashSet<int> setAdminCode2 = new HashSet<int>();
            foreach (GeoEntity geoEntity in geoEntityList2)
            {
                if (geoEntity.iLevel <= (int)Common.GEO_ENTITY_TYPE.ADMIN3)
                {
                    foreach (int item in geoEntity.AdminCodeList)
                    {
                        setAdminCode2.Add(item);
                    }
                }
            }

            //Get all possible admin code of geo-entity 1
            Queue<int> qAdminCode1 = new Queue<int>();
            foreach (GeoEntity geoEntity in geoEntityList1)
            {
                if (geoEntity.iLevel <= (int)Common.GEO_ENTITY_TYPE.ADMIN3)
                {
                    foreach (int item in geoEntity.AdminCodeList)
                    {
                        qAdminCode1.Enqueue(item);
                    }
                }
            }

            while (qAdminCode1.Count > 0)
            {
                int admin = qAdminCode1.Dequeue();
                if (setAdminCode2.Contains(admin) == true)
                {
                    return true;
                }

                if (adminCode2PreCode.ContainsKey(admin) == true)
                {
                    qAdminCode1.Enqueue(adminCode2PreCode[admin]);
                }
            }
            return false;
        }

        //Load geo-entity data from file   
        private bool LoadGeoData(string strLocationName, out DoubleArrayTrieSearch dartSearch, out List<List<GeoEntity>> vaList)
        {
            dartSearch = new DoubleArrayTrieSearch();
            dartSearch.Load(strLocationName + ".dart");

            GeoEntityStorage geoItemStorage = new GeoEntityStorage();
            vaList = geoItemStorage.LoadFromFile(strLocationName + ".dat");

            return true;
        }


        public bool Initialize()
        {
            dictmatch = new DictMatch();
            dm_r = new List<Lemma>();
            dm_offsetList = new List<int>();

            Dictionary<string, string> confDict;
            confDict = LoadConfFile("BusinessNameParserFeatureGenerator.ini");

            LoadGeoData(confDict[KEY_GEO_ENTITY_FILE_NAME.ToLower()], out dartSearch_GeoEntity, out vaGeoEntityList);
            LoadAdminCodeRelationship(confDict[KEY_ADMIN_CODE_FILE_NAME.ToLower()]);

            string strDictMatchFileName = confDict[KEY_LEXICAL_DICT_FILE_NAME.ToLower()];
            if (strDictMatchFileName.Length == 0)
            {
                return true;
            }

            bool bBinaryDict = bool.Parse(confDict[KEY_BINARY_DICT.ToLower()]);
            if (bBinaryDict == true)
            {
                dictmatch.LoadDictFromBinary(strDictMatchFileName);
            }
            else
            {
                dictmatch.LoadDictFromRawText(strDictMatchFileName);
            }
            return true;
        }

        //Generate feature set for POI parser
        public List<List<string>> GenerateFeature(string strText)
        {
            List<List<string>> rstListList = new List<List<string>>();
            if (dictmatch == null)
            {
                return rstListList;
            }

            dm_r.Clear();
            dm_offsetList.Clear();
            dictmatch.Search(strText, ref dm_r, ref dm_offsetList, DictMatch.DM_OUT_FMM);

            string[] astrDictMatch;
            astrDictMatch = new string[strText.Length];

            //Build lexical feature
            List<GeoDictMatchResult> geoDictMatchRstList = new List<GeoDictMatchResult>();
            for (int i = 0; i < dm_r.Count; i++)
            {
                int offset = dm_offsetList[i];
                int len = (int)dm_r[i].len;
                List<string> tagList = new List<string>(dm_r[i].strProp.Split('|'));

                for (int j = offset; j < offset + len; j++)
                {
                    if (dm_r[i].strProp.Contains("DICT_LOC_ADMIN1") == true ||
                        dm_r[i].strProp.Contains("DICT_LOC_ADMIN2") == true ||
                        dm_r[i].strProp.Contains("DICT_LOC_ADMIN3") == true ||
                        (dm_r[i].strProp.Contains("DICT_LOC_ADMIN") == true && len >= 3))
                    {
                        astrDictMatch[j] = "LOC";
                    }
                    else if ((tagList.Contains("ORG_MOD") == true || tagList.Contains("M") == true) && len > 1)
                    {
                        astrDictMatch[j] = "ORG_MOD";
                    }

                }

                if (dm_r[i].strProp.Contains("DICT_LOC_ADMIN1") == true ||
                    dm_r[i].strProp.Contains("DICT_LOC_ADMIN2") == true ||
                    dm_r[i].strProp.Contains("DICT_LOC_ADMIN3") == true ||
                    (dm_r[i].strProp.Contains("DICT_LOC_ADMIN") == true && len >= 3))
                {
                    GeoDictMatchResult geoDMRst = new GeoDictMatchResult();
                    geoDMRst.len = len;
                    geoDMRst.offset = offset;
                    geoDMRst.hasRelation = 0;
                    geoDMRst.strGeoName = strText.Substring(offset, len);

                    geoDictMatchRstList.Add(geoDMRst);
                }
            }

            //Get sub business name's suffix list
            List<SubSuffixToken> sstTokenList;
            sstTokenList = GetSubSuffixTagList(strText);
            foreach (SubSuffixToken item in sstTokenList)
            {
                for (int i = item.offset; i < item.offset + item.len; i++)
                {
                    astrDictMatch[i] = "ORG_SUB_SUFFIX";
                }
            }


            //Build location relationship feature
            for (int i = 0; i < geoDictMatchRstList.Count; i++)
            {
                for (int j = i + 1; j < geoDictMatchRstList.Count; j++)
                {
                    if (IsParent(geoDictMatchRstList[i].strGeoName, geoDictMatchRstList[j].strGeoName) == true ||
                        IsParent(geoDictMatchRstList[j].strGeoName, geoDictMatchRstList[i].strGeoName) == true)
                    {
                        geoDictMatchRstList[i].hasRelation = 1;
                        geoDictMatchRstList[j].hasRelation = 1;
                    }
                }
            }

            //Check whether detected location is sub business name's location
            bool bHasSubORGLoc = false;
            foreach (GeoDictMatchResult item in geoDictMatchRstList)
            {
                //If the location has no relationship with others AND
                //the term which is next to the location is sub business name's suffix,
                //then the location maybe sub business name location.
                if (item.hasRelation == 0 &&
                    item.offset + item.len < strText.Length - 1 &&
                    astrDictMatch[item.offset + item.len + 1] == "ORG_SUB_SUFFIX"
                    )
                {
                    bHasSubORGLoc = true;
                    for (int i = item.offset; i < item.offset + item.len; i++)
                    {
                        astrDictMatch[i] = "SUB_ORG_LOC";
                    }
                }
            }

            //If sub business name location is existed, the real location is the sub location,
            //And rewrite other locations as modified location type
            if (bHasSubORGLoc == true)
            {
                for (int i = 0; i < astrDictMatch.Length; i++)
                {
                    if (astrDictMatch[i] == "LOC")
                    {
                        astrDictMatch[i] = "ORG_MOD_LOC";
                    }
                    else if (astrDictMatch[i] == "SUB_ORG_LOC")
                    {
                        astrDictMatch[i] = "LOC";
                    }
                }

            }


            //Fill all cloumns for each token
            for (int i = 0; i < strText.Length; i++)
            {
                rstListList.Add(new List<string>());
                rstListList[i].Add(strText[i].ToString());

                if (astrDictMatch[i] != null)
                {
                    rstListList[i].Add(astrDictMatch[i]);
                }
                else
                {
                    rstListList[i].Add("NOR");
                }
            }


            return rstListList;
        }
    }
}
